#include "Wt.h"

/* Cookie type */

struct Tcl_ObjType WtCookieType =
{
  "cookie",
  WtCookieFree,
  WtCookieDup,
  WtCookieUpdateString,
  WtCookieSetFromAny
};

/* Create a new cookie */

Tcl_Obj *WtNewCookieObj()
{
  Tcl_Obj *obj = Tcl_NewObj();
  WtCookie *cookieRep = (WtCookie *)ckalloc(sizeof(WtCookie));
  cookieRep->name = Tcl_NewStringObj("", 0);
  cookieRep->expires = Tcl_NewStringObj("", 0);
  cookieRep->domain = Tcl_NewStringObj("", 0);
  cookieRep->path = Tcl_NewStringObj("", 0);
  cookieRep->secure = Tcl_NewBooleanObj(0);
  cookieRep->values = Tcl_NewListObj(0, NULL);
  obj->internalRep.twoPtrValue.ptr1 = cookieRep;
  obj->typePtr = &WtCookieType;
  Tcl_InvalidateStringRep(obj);
  return obj;
}

/* Get the internal rep */

WtCookie *WtCookieRep(Tcl_Obj *obj)
{
  return (WtCookie *)(obj->internalRep.twoPtrValue.ptr1);
}

/* Copy a cookie */

static void WtCookieDup(Tcl_Obj *src, Tcl_Obj *dst)
{
  WtCookie *srcRep = WtCookieRep(src);
  WtCookie *dstRep = (WtCookie *)ckalloc(sizeof(WtCookie));
  dstRep->name = Tcl_DuplicateObj(srcRep->name);
  dstRep->expires = Tcl_DuplicateObj(srcRep->expires);
  dstRep->domain = Tcl_DuplicateObj(srcRep->domain);
  dstRep->path = Tcl_DuplicateObj(srcRep->path);
  dstRep->secure = Tcl_DuplicateObj(srcRep->secure);
  dstRep->values = Tcl_DuplicateObj(srcRep->values);
  dst->internalRep.twoPtrValue.ptr1 = dstRep;
  dst->typePtr = &WtCookieType;
}

/* Update the string rep */

static void WtCookieUpdateString(Tcl_Obj *obj)
{
  WtUpdatePtrString(obj);
}

/* Convert an obj to a cookie */

static int WtCookieSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetPtrFromAny(interp, obj, &WtCookieType);
}

/* Free a cookie */

static void WtCookieFree(Tcl_Obj *obj)
{
  WtCookie *cookie = WtCookieRep(obj);
  WtDeletePtr(obj);
  Tcl_DecrRefCount(cookie->name);
  Tcl_DecrRefCount(cookie->expires);
  Tcl_DecrRefCount(cookie->domain);
  Tcl_DecrRefCount(cookie->path);
  Tcl_DecrRefCount(cookie->secure);
  Tcl_DecrRefCount(cookie->values);
  ckfree((char *)(WtCookieRep(obj)));
}

/* Set the name */

int WtCookieSetName(Tcl_Obj *cookieObj, Tcl_Obj *name, Tcl_Interp *interp)
{
  WtCookie *cookie = WtCookieRep(cookieObj);
  if (cookie->name) {
    Tcl_DecrRefCount(cookie->name);
  }
  cookie->path = path;
  Tcl_IncrRefCount(cookie->name);
  return 1;
}

/* Set the value */

int WtCookieSetValue(Tcl_Obj *cookieObj, Tcl_Obj *value, Tcl_Interp *interp)
{
  WtCookie *cookie = WtCookieRep(cookieObj);
  ap_assert(Tcl_SetListObj(cookie->values, 1, &value) == TCL_OK);
  return 1;
}

/* Set all values */

int WtCookieSetValues(Tcl_Obj *cookieObj, Tcl_Obj *values, Tcl_Interp *interp)
{
  WtCookie *cookie = WtCookieRep(cookieObj);
  if (cookie->values) {
    Tcl_DecrRefCount(cookie->values);
  }
  cookie->values = values;
  Tcl_IncrRefCount(cookie->values);
  return 1;
}

/* Set the expiration time */

int WtCookieSetExpires(Tcl_Obj *cookieObj, Tcl_Obj *expires, Tcl_Interp *interp)
{
  const char *str;
  WtContext *w = WtGetInterpContext(interp);

  str = ApacheUtil_expires(w->active->request.apReq->pool, expires, EXPIRES_COOKIE);

  if (!str) {
    Tcl_AppendResult(interp, "Expiration time string is invalid.");
  } else {
    cookie = WtCookieRep(cookieObj);
    if (cookie->expires) {
      Tcl_DecrRefCount(cookie->expires);
    }
    cookie->expires = WtNewString(str);
    Tcl_IncrRefCount(cookie->expires);
    return 1;
  }

  return 0;
}

/* Set the domain */

int WtCookieSetDomain(Tcl_Obj *cookieObj, Tcl_Obj *domain, Tcl_Interp *interp)
{
  WtCookie *cookie = WtCookieRep(cookieObj);
  if (cookie->domain) {
    Tcl_DecrRefCount(cookie->domain);
  }
  cookie->domain = domain;
  Tcl_IncrRefCount(cookie->domain);
  return 1;
}

/* Set the path */

int WtCookieSetPath(Tcl_Obj *cookieObj, Tcl_Obj *path, Tcl_Interp *interp)
{
  WtCookie *cookie = WtCookieRep(cookieObj);
  if (cookie->path) {
    Tcl_DecrRefCount(cookie->path);
  }
  cookie->path = path;
  Tcl_IncrRefCount(cookie->path);
  return 1;
}

/* Set the secure option */

int WtCookieSetSecure(Tcl_Obj *cookieObj, int secure, Tcl_Interp *interp)
{
  WtCookie *cookie = WtCookieRep(cookieObj);
  if (cookie->secure) {
    Tcl_DecrRefCount(cookie->secure);
  }
  cookie->secure = Tcl_NewBooleanObj(secure);
  Tcl_IncrRefCount(cookie->secure);
  return 1;
}

/* cookie command */

Tcl_Obj *WtCookieCmdGetCookie(Tcl_Interp *interp, Tcl_Obj *obj)
{
  Tcl_Obj *found = WtGetPtr(obj, &WtCookieType);

  if (!found) {
    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
      "Parameter is not a valid cookie object.", NULL);
  }

  return found;
}

void WtCookieUsage(Tcl_Interp *interp, Tcl_Obj *cmd)
{
  Tcl_Obj *res = Tcl_GetObjResult(interp);
  char *cmdStr = WtToString(cmd);
  Tcl_AppendStringsToObj(res, "Incorrect usage. Should be:\n",
    cmdStr, " create\n",
    cmdStr, " name cookie ?name?\n",
    cmdStr, " expires cookie ?expiresDate?\n",
    cmdStr, " domain cookie ?domain?\n",
    cmdStr, " path cookie ?path?\n",
    cmdStr, " secure cookie ?secureFlag?\n",
    cmdStr, " value cookie ?value?\n",
    cmdStr, " allValues cookie ?valueList?\n",
    cmdStr, " table cookie\n",
    NULL);
}

int WtCookieCmd(ClientData clientData, Tcl_Interp *interp, int objc,
    Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, i, optsDone = 0, error = 0, intVal;
  WtContext *w;
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp);
  char *subCmd, *expiresStr;
  const char *argStr;
  Tcl_Obj *cookieObj, *valObj, *tbl;
  WtCookie *cookie;

  w = (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);

  if (objc < 2) {

    WtCookieUsage(interp, objv[0]);
    return TCL_ERROR;

  }

  subCmd = WtToString(objv[1]);

  if (!strcmp(subCmd, "create")) {

    /* cookie create command */

    cookieObj = WtNewCookieObj();
    Tcl_IncrRefCount(cookieObj);
    cookie = WtCookieRep(cookieObj);

    for (i = 2; i < objc; i++) {
      argStr = WtToString(objv[i]);
      if (!strcmp(argStr, "-expires")) {
        if (i + 1 >= objc) {
          Tcl_AppendStringsToObj(interpRes, "The \"-expires\" option requires a value.");
          error = 1;
          break;
        } else {
          if (!WtCookieSetExpires(cookieObj, objv[i + 1], interp)) {
            error = 1;
            break;
          } else {
            i++;
          }
        }
      } else if (!strcmp(argStr, "-domain")) {
        if (i + 1 >= objc) {
          Tcl_AppendStringsToObj(interpRes, "The \"-domain\" option requires a value.");
          error = 1;
          break;
        } else {
          WtCookieSetDomain(cookieObj, objv[i + 1], interp);
          i++;
        }
      } else if (!strcmp(argStr, "-path")) {
        if (i + 1 >= objc) {
          Tcl_AppendStringsToObj(interpRes, "The \"-path\" option requires a value.");
          error = 1;
          break;
        } else {
          WtCookieSetPath(cookieObj, objv[i + 1], interp);
          i++;
        }
      } else if (!strcmp(argStr, "-secure")) {
        if (i + 1 >= objc) {
          Tcl_AppendStringsToObj(interpRes, "The \"-secure\" option requires a "
            "boolean value.");
          error = 1;
          break;
        } else {
          if (Tcl_GetBooleanFromObj(interp, objv[2], &intVal) != TCL_OK) {
            Tcl_AppendStringsToObj(interpRes, "Boolean value is invalid.");
            error = 1;
            break;
          } else {
            WtCookieSetSecure(cookieObj, Tcl_NewBooleanObj(intVal), interp);
            i++;
          }
        }
      } else if (!strcmp(argStr, "--")) {
        optsDone = 1;
        break;
      } else if (argStr && argStr[0] == '-') {
        Tcl_AppendStringsToObj(interpRes, "Option \"", argStr,
          "\" is invalid.");
        WtCookieUsage(interp, objv[0]);
        error = 1;
        break;
      } else {
        optsDone = 1;
        break;
      }
    }

    if (!error) {
      if (i >= objc) {
        WtCookieUsage(interp, objv[0]);
        error = 1;
      } else if (i + 2 < objc) {
        Tcl_AppendStringsToObj(interpRes, "Extra parameters found at the end. ");
        WtCookieUsage(interp, objv[0]);
        error = 1;
      } else {
        WtCookieSetName(cookieObj, objv[i], interp);
        if (i + 1 < objc) {
          WtCookieSetValue(cookieObj, objv[i + 1], interp);
        }
      }
    }

    if (!error) {
      Tcl_SetObjResult(interp, cookieObj);
      Tcl_DecrRefCount(cookieObj);
      ret = TCL_OK;
    } else {
      Tcl_DecrRefCount(cookieObj);
    }

  } else if (!strcmp(subCmd, "name")) {

    /* name command */

    if (objc != 3 && objc != 4) {
     Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " cookie ?name?", NULL);
    } else {
      cookieObj = WtCookieCmdGetCookie(interp, objv[2]);
      if (cookieObj) {
        cookie = WtCookieRep(cookieObj);
        if (objc == 3) {
          Tcl_SetObjResult(interp, cookie->name);
          ret = TCL_OK;
        } else if (objc == 4) {
          WtCookieSetName(cookieObj, objv[3], interp);
          Tcl_SetObjResult(interp, cookie->name);
          ret = TCL_OK;
        }
      }
    }

  } else if (!strcmp(subCmd, "value")) {

    /* value command */

    if (objc != 3 && objc != 4) {
     Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " cookie ?value?", NULL);
    } else {
      cookieObj = WtCookieCmdGetCookie(interp, objv[2]);
      if (cookieObj) {
        cookie = WtCookieRep(cookieObj);
        if (objc == 4) {
          WtCookieSetValue(cookieObj, objv[3], interp);
        }
        valObj = WtGetFirstListElement(cookie->values);
        Tcl_SetObjResult(interp, valObj ? valObj : WtNewString(""));
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "allValues")) {

    /* allValues command */

    if (objc != 3 && objc != 4) {
     Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " cookie ?valueList?", NULL);
    } else {
      cookieObj = WtCookieCmdGetCookie(interp, objv[2]);
      if (cookieObj) {
        cookie = WtCookieRep(cookieObj);
        if (objc == 3) {
          Tcl_SetObjResult(interp, cookie->values);
          ret = TCL_OK;
        } else {
          if (Tcl_ListObjLength(NULL, objv[3], &intVal) != TCL_OK) {
            Tcl_AppendStringsToObj(interpRes, "Values is not a valid list.");
          } else {
            WtCookieSetValues(cookieObj, objv[3], interp);
            Tcl_SetObjResult(interp, cookie->values);
            ret = TCL_OK;
          }
        }
      }
    }

  } else if (!strcmp(subCmd, "domain")) {

    /* domain command */

    if (objc != 3 && objc != 4) {
     Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " cookie ?domain?", NULL);
    } else {
      cookieObj = WtCookieCmdGetCookie(interp, objv[2]);
      if (cookieObj) {
        cookie = WtCookieRep(cookieObj);
        if (objc == 4) {
          WtCookieSetDomain(cookieObj, objv[3], interp);
        }
        Tcl_SetObjResult(interp, cookie->domain);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "expires")) {

    /* expires command */

    if (objc != 3 && objc != 4) {
     Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " cookie ?expiresDate?", NULL);
    } else {
      cookieObj = WtCookieCmdGetCookie(interp, objv[2]);
      if (cookieObj) {
        cookie = WtCookieRep(cookieObj);
        if (objc == 4) {
          WtCookieSetExpires(cookieObj, objv[3], interp);
        }
        Tcl_SetObjResult(interp, cookie->expires);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "path")) {

    /* path command */

    if (objc != 3 && objc != 4) {
     Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " cookie ?path?", NULL);
    } else {
      cookieObj = WtCookieCmdGetCookie(interp, objv[2]);
      if (cookieObj) {
        cookie = WtCookieRep(cookieObj);
        if (objc == 4) {
          WtCookieSetPath(cookieObj, objv[3], interp);
        }
        Tcl_SetObjResult(interp, cookie->path);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "secure")) {

    /* secure command */

    if (objc != 3 && objc != 4) {
     Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " cookie ?secureFlag?", NULL);
    } else {
      cookieObj = WtCookieCmdGetCookie(interp, objv[2]);
      if (cookieObj) {
        cookie = WtCookieRep(cookieObj);
        if (objc == 3) {
          Tcl_SetObjResult(interp, cookie->secure);
          ret = TCL_OK;
        } else if (objc == 4) {
          if (Tcl_GetBooleanFromObj(interp, objv[3], &intVal) != TCL_OK) {
            Tcl_AppendStringsToObj(interpRes, "Boolean value is invalid.");
          } else {
            WtCookieSetSecure(cookieObj, Tcl_NewBooleanObj(intVal), interp);
            Tcl_SetObjResult(interp, cookie->secure);
            ret = TCL_OK;
          }
        }
      }
    }

  } else if (!strcmp(subCmd, "table")) {

    /* table command - create a table with the item's properties */

    if (objc != 3) {
     Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " cookie", NULL);
    } else {
      cookieObj = WtCookieCmdGetCookie(interp, objv[2]);
      if (cookieObj) {
        cookie = WtCookieRep(cookieObj);
        tbl = WtNewTableObj(NULL);
        WtTableSetStrToObj(tbl, "name", cookie->name);
        WtTableSetStrToObj(tbl, "expires", cookie->expires);
        WtTableSetStrToObj(tbl, "domain", cookie->domain);
        WtTableSetStrToObj(tbl, "path", cookie->path);
        WtTableSetStrToObj(tbl, "secure", cookie->secure);
        WtTableSetStrToObj(tbl, "values", cookie->values);
        Tcl_SetObjResult(interp, tbl);
        ret = TCL_OK;
      }
    }

  } else {

    /* Unknown command */

    WtCookieUsage(interp, objv[0]);

  }

  return ret;
}

/* Cookie collection object */

static int WtCookieClnCompare(Tcl_Obj *item, Tcl_Obj *key)
{
  WtCookie *cookie = WtCookieRep(item);
  return strcmp(WtSafeStr(WtToString(cookie->name)),
    WtSafeStr(WtToString(key)));
}

static Tcl_Obj *WtCookieClnGetKey(Tcl_Obj *item)
{
  WtCookie *cookie = WtCookieRep(item);
  return cookie->name;
}

static Tcl_Obj *WtCookieClnGetValue(Tcl_Obj *item)
{
  return item;
}

WtClnItemType WtCookieClnType = {
  WtCookieClnCompare,
  WtCookieClnGetKey,
  WtCookieClnGetValue
};

Tcl_Obj *WtNewCookieClnObj(char *name)
{
  Tcl_Obj *obj = WtNewClnObj(&WtCookieClnType);
  if (name) {
    WtSetPtrId(obj, name, -1);
  }
  return obj;
}

/* cookies command */

int WtResponseCookiesCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret;
  WtContext *w = WtGetContext();

  int status = WtClnMethodHelper(clientData, interp, objc,
    objv, &ret, w->active->response.cookies, 0, 1);

  return ret;
}


/* cookieCollection command */

int WtCookieClnCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret;
  WtClnCmdHelper(clientData, interp, objc, objv, &ret,
    &WtCookieClnType, 0, 1);
  return ret;
}

/* Initialize cookie commands */

void WtInitCookieCommands(Tcl_Interp *interp)
{
  WtContext *w = Tcl_GetAssocData(interp, "wt::context", NULL);

  WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
    "Wtcl: WtInitCookieCommands.");

  Tcl_RegisterObjType(&WtCookieType);

  Tcl_CreateObjCommand(interp, "::wt::cookie",
                       WtCookieCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::responseCookies",
                       WtResponseCookiesCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::cookieCollection",
                       WtCookieClnCmd, NULL, NULL);
}
